package cdp.tarasenko.task1;

/**
 * @Author( name = oleksandr
 * date = 7/27/13 )
 */
public class Dequeue<T> {

    private static final int DEFAULT_SIZE = 16;

    private int initCapacity = 0;

    protected Stack<T> first;
    protected Stack<T> second;

    public Dequeue() {
        this(DEFAULT_SIZE);
    }

    public Dequeue(int initCapacity) {
        this.initCapacity = initCapacity;
        initStacks();
    }

    public int getSize() {
        return first.getSize() + second.getSize();
    }


    public int getCapacity() {
        return first.getCapacity() + second.getCapacity();
    }

    public T getLeft() {
        if (getSize() == 0) throw new MyCollectionsException("You can not get item from empty dequeue.");

        T item;
        if (first.getSize() != 0) {
            item = first.pull();
        } else {
            item = pullLastItem(second);
        }
        balance();
        return item;
    }

    public T getRight() {
        if (getSize() == 0) throw new MyCollectionsException("You can not get item from empty dequeue.");

        T item;
        if (second.getSize() != 0) {
            item = second.pull();
        } else {
            item = pullLastItem(first);
        }
        balance();
        return item;
    }

    public void addLeft(T item) {
        first.push(item);
        balance();
    }

    public void addRight(T item) {
        second.push(item);
        balance();
    }

    public void clear() {
        initStacks();
    }

    protected void balance() {
        if (this.getSize() < 3) return;

        int firstSize = first.getSize();
        int secondSize = second.getSize();

        Stack<T> newFirst;
        Stack<T> newSecond;
        if (firstSize * 3 < secondSize) {
            newFirst = new Stack<T>(first.getCapacity());
            newSecond = new Stack<T>(second.getCapacity());
            balance(first,newFirst,second,newSecond);
        } else if (secondSize * 3 < firstSize) {
            newFirst = new Stack<T>(first.getCapacity());
            newSecond = new Stack<T>(second.getCapacity());
            balance(second,newSecond,first,newFirst);
        } else return;


        first = newFirst;
        second = newSecond;
    }

    protected void balance(Stack<T> smallSrc, Stack<T> smallDest, Stack<T> bigSrc, Stack<T> bigDest) {
        int smallSize = smallSrc.getSize();
        int bigSize = bigSrc.getSize();
        int numberToBeMoved = (bigSize - smallSize) / 2;

        Object[] smallElements = new Object[smallSize];
        Object[] bigElements = new Object[bigSize];
        for (int i = 0; i < smallSize; i++) {
            smallElements[i] = smallSrc.pull();
        }
        for (int i = 0; i < bigSize; i++) {
            bigElements[i] = bigSrc.pull();
        }

        int lastElemToBeCopied = bigSize - numberToBeMoved;
        for (int currBigElementIndex = bigSize - 1; currBigElementIndex >= lastElemToBeCopied; currBigElementIndex--) {
            smallDest.push((T) bigElements[currBigElementIndex]);
        }
        for (int currSmallElementIndex = smallSize-1; currSmallElementIndex >= 0; currSmallElementIndex--) {
            smallDest.push((T) smallElements[currSmallElementIndex]);
        }

        for (int currBigElementIndex = lastElemToBeCopied - 1; currBigElementIndex >= 0; currBigElementIndex--) {
            bigDest.push((T) bigElements[currBigElementIndex]);
        }
    }

    protected T pullLastItem(Stack<T> stack) {
        int size = stack.getSize();
        Object[] elements = new Object[size];
        for (int i = 0; i < size; i++) {
            elements[i] = stack.pull();
        }
        for (int i = size - 2; i >= 0; i--) {
            stack.push((T)elements[i]);
        }
        return (T)elements[size-1];
    }

    private void initStacks() {
        int firstSize = initCapacity /2;
        int secondSize;
        if (initCapacity % 2 == 0) {
            secondSize = firstSize;
        } else {
            secondSize = firstSize + 1;
        }
        first = new Stack<T>(firstSize);
        second = new Stack<T>(secondSize);
    }

}
